﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using RSSFeedReader.Data.Models;
using RSSFeedReader.Resources;
using RSSFeedReader.ViewModels.Messages;

namespace RSSFeedReader.ViewModels
{
    /// <summary>
    /// View Model only exposes a subset of the underlying
    /// Channel model properties.
    /// </summary>
    public class ChannelViewModel : MyViewModelBase
    {
        #region Fields
        readonly Channel _channel;
        readonly ObservableCollection<PostViewModel> _posts;
        ObservableCollection<ChannelViewModel> _children;
        ChannelViewModel _parent;
        PostViewModel _selectedPost;
        bool _isSelected;
        bool _isLoading;
        bool _isExpanded;
        int _newPosts;
        int _folderNewPosts;

        #endregion

        #region Constructor

        /// <summary>
        /// Initialize a new instance of the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> class.
        /// </summary>
        public ChannelViewModel()
        {
        }

        /// <summary>
        /// Initialize a new instance of the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> class 
        /// with a reference to the <see cref="RSSFeedReader.Data.Models.Channel"/> instance
        /// and the ObservableCollection of type <see cref="RSSFeedReader.ViewModels.PostViewModel"/>.
        /// </summary>
        /// <param name="channel">The <see cref="RSSFeedReader.Data.Models.Channel"/> instance 
        /// that this <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance relates to.</param>
        /// <param name="posts">The ObservableCollection of type <see cref="RSSFeedReader.ViewModels.PostViewModel"/>
        /// that relates to this <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance.</param>
        /// <exception cref="System.ArgumentNullException">Null argument passed in.</exception>
        public ChannelViewModel(Channel channel, ObservableCollection<PostViewModel> posts)
            : this(channel, posts, null)
        {
        }

        /// <summary>
        /// Initialize a new instance of the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> class 
        /// with a reference to the <see cref="RSSFeedReader.Data.Models.Channel"/> instance
        /// and the ObservableCollection of type <see cref="RSSFeedReader.ViewModels.PostViewModel"/>.
        /// </summary>
        /// <param name="channel">
        /// The <see cref="RSSFeedReader.Data.Models.Channel"/> instance 
        /// that this <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance relates to.
        /// </param>
        /// <param name="posts">
        /// The ObservableCollection of type <see cref="RSSFeedReader.ViewModels.PostViewModel"/>
        /// that relates to this <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance.
        /// </param>
        /// <param name="parent">
        /// The <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance that is
        /// the parent of this <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance.
        /// </param>
        /// <exception cref="System.ArgumentNullException">
        /// Null argument passed in.
        /// </exception>
        private ChannelViewModel(Channel channel, ObservableCollection<PostViewModel> posts,
                    ChannelViewModel parent)
        {
            if (channel == null)
                throw new ArgumentNullException("channel");

            if (posts == null)
                throw new ArgumentNullException("posts");

            _channel = channel;
            _posts = posts;
            _parent = parent;
            _selectedPost = null;
            Initialize();
        }

        /// <summary>
        /// Initialize data.
        /// </summary>
        void Initialize()
        {
            // Hook up events
            ChannelDataSource.Instance.ChannelSynchronised += OnChannelSynched;
            _posts.CollectionChanged += OnPostViewModelCollectionChanged;

            _children = new ObservableCollection<ChannelViewModel>(
                (from child in _channel.Children
                 select new ChannelViewModel(child, PostViewModel.CreatePostViewModel(child.Posts), this)
                 ).ToList<ChannelViewModel>());

            if (ChannelType == ChannelType.Feed)
            {
                if (_posts.Count > 0)
                {
                    _newPosts = _posts.Count(pvm => pvm.IsRead == false);
                    foreach (PostViewModel pvm in _posts)
                        pvm.PropertyChanged += OnPostViewModelPropertyChanged;
                }
            }
            else
            {
                GetPostsChildrenCount(this);
                _newPosts = _folderNewPosts;
            }
        }

        #endregion

        #region Commands

        #region AddNewFolderCommand
        private RelayCommand _addNewFolderCommand;

        /// <summary>
        /// Gets the <see cref="AddNewFolderCommand"/>.
        /// </summary>
        public RelayCommand AddNewFolderCommand
        {
            get
            {
                return _addNewFolderCommand ?? (_addNewFolderCommand = new RelayCommand(() => 
                    Messenger.Default.Send(new ShowWindowMessage<AddNewFolderViewModel>(new AddNewFolderViewModel(_channel)))));
                
            }
        }
        #endregion

        #region MarkAllAsReadCommand
        private RelayCommand _markAllAsReadCommand;

        /// <summary>
        /// Gets the <see cref="MarkAllAsReadCommand"/>.
        /// </summary>
        public RelayCommand MarkAllAsReadCommand
        {
            get
            {
                return _markAllAsReadCommand ?? (_markAllAsReadCommand = new RelayCommand(
                    () =>
                    {
                        if (ChannelType == ChannelType.Feed)
                        {
                            foreach (PostViewModel pvm in Posts)
                            {
                                pvm.IsRead = true;
                            }
                            UpdateNewPosts(this);
                        }
                        else
                        {
                            foreach (ChannelViewModel cvm in Children)
                            {
                                foreach (PostViewModel pvm in cvm.Posts)
                                {
                                    pvm.IsRead = true;
                                }
                                UpdateNewPosts(cvm, false);
                            }
                            if (ChannelType == ChannelType.Folder)
                            {
                                UpdateNewPosts(this);
                            }
                        }
                    },
                    () =>
                    {
                        //return NewPosts > 0;
                        if (ChannelType == ChannelType.Feed)
                        {
                            return NewPosts > 0;
                        }
                        return Children.Count(x => x.NewPosts > 0) > 0;
                    }));
            }
        }
        #endregion

        #region SyncChannelCommand
        private RelayCommand _syncChannelCommand;

        /// <summary>
        /// Gets the <see cref="SyncChannelCommand"/>.
        /// </summary>
        public RelayCommand SyncChannelCommand
        {
            get
            {
                return _syncChannelCommand ?? (_syncChannelCommand = new RelayCommand(
                    () =>
                    {
                        IsLoading = true;
                        ChannelDataSource.Instance.SyncChannel(ChannelID);
                    },
                    () => ChannelType == ChannelType.Feed &&
                          ChannelDataSource.Instance.IsNetworkAvailable));
            }
        }
        #endregion

        #region SyncAllChannelsCommand
        private RelayCommand _syncAllChannelsCommand;

        /// <summary>
        /// Gets the <see cref="SyncAllChannelsCommand"/>.
        /// </summary>
        public RelayCommand SyncAllChannelsCommand
        {
            get
            {
                return _syncAllChannelsCommand ?? (_syncAllChannelsCommand = new RelayCommand(
                    () =>
                    {
                        foreach (var channelViewModel in MainViewModel.ChannelCollection)
                        {
                            SetIsLoading(channelViewModel);
                        }
                        ChannelDataSource.Instance.SyncAllChannels();
                    },
                    () => ChannelDataSource.Instance.IsNetworkAvailable));
            }
        }
        #endregion

        #region DeleteChannelCommand
        private RelayCommand _deleteChannelCommand;

        /// <summary>
        /// Gets the <see cref="DeleteChannelCommand"/>.
        /// </summary>
        public RelayCommand DeleteChannelCommand
        {
            get
            {
                return _deleteChannelCommand ?? (_deleteChannelCommand = new RelayCommand(
                    () =>
                    {
                        var message = string.Format(ChannelType == ChannelType.Feed ? Strings.DeleteFeedConfirmation : Strings.DeleteFolderConfirmation, Title);
                        ShowDialogMessage(
                            message,
                            Strings.ApplicationName,
                            MessageBoxButton.YesNo,
                            MessageBoxImage.Warning,
                            dialogResult =>
                            {
                                if (dialogResult == MessageBoxResult.Yes)
                                {
                                    ChannelDataSource.Instance.RemoveChannel(ChannelID);
                                }
                            });
                    }));
            }
        }
        #endregion

        #region RenameChannelCommand
        private RelayCommand _renameChannelCommand;

        /// <summary>
        /// Renames a Channel.
        /// </summary>
        public RelayCommand RenameChannelCommand
        {
            get
            {
                return _renameChannelCommand ?? (_renameChannelCommand = new RelayCommand(
                    () => Messenger.Default.Send(new ShowWindowMessage<RenameChannelViewModel>(new RenameChannelViewModel(this)))));
            }
        }
        #endregion

        #region MoveToFolderCommand
        private RelayCommand _moveToFolderCommand;

        /// <summary>
        /// Moves a Channel to a specified folder.
        /// </summary>
        public RelayCommand MoveToFolderCommand
        {
            get { return _moveToFolderCommand ?? (_moveToFolderCommand = new RelayCommand(() => 
                Messenger.Default.Send(new ShowWindowMessage<MoveToFolderViewModel>(new MoveToFolderViewModel(this))))); }
        }
        #endregion

        #region ChannelPropertiesCommand
        private RelayCommand _channelPropertiesCommand;

        /// <summary>
        /// Gets the <see cref="ChannelPropertiesCommand"/>.
        /// </summary>
        public RelayCommand ChannelPropertiesCommand
        {
            get
            {
                return _channelPropertiesCommand ?? (_channelPropertiesCommand = new RelayCommand(
                    () => Messenger.Default.Send(new ShowWindowMessage<ChannelPropertiesViewModel>(new ChannelPropertiesViewModel(this))),
                    () => ChannelType == ChannelType.Feed));
            }
        }
        #endregion

        private RelayCommand _addNewFeedCommand;

        /// <summary>
        /// Gets the <see cref="AddNewFeedCommand"/>.
        /// </summary>
        public RelayCommand AddNewFeedCommand
        {
            get
            {
                return _addNewFeedCommand ?? (_addNewFeedCommand = new RelayCommand(
                    () => Messenger.Default.Send(new ShowWindowMessage<AddNewFeedViewModel>(new AddNewFeedViewModel(_channel))),
                    () => ChannelType == ChannelType.Folder));
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the ChannelID of the channel.
        /// </summary>
        public Guid ChannelID
        {
            get { return _channel.ChannelID; }
        }

        /// <summary>
        /// Gets the title of the channel.
        /// </summary>
        public string Title
        {
            get { return _channel.Title; }
            set
            {
                if (_channel.Title == value.Trim())
                {
                    return;
                }
                _channel.Title = value;
                RaisePropertyChanged("Title");
            }
        }

        /// <summary>
        /// Gets the Url of the channel.
        /// </summary>
        public string Url
        {
            get { return _channel.Url; }
        }

        /// <summary>
        /// The Image associated with the feed.
        /// </summary>
        public string ImageUrl
        {
            get { return _channel.ImageUrl; }
        }

        /// <summary>
        /// Gets the link to the channel.
        /// </summary>
        public string Link
        {
            get { return _channel.Link; }
        }

        /// <summary>
        /// Gets the summary of the channel.
        /// </summary>
        public string Description
        {
            get { return _channel.Description; }
        }

        /// <summary>
        /// Gets the ObservableCollection of type <see cref="RSSFeedReader.ViewModels.PostViewModel"/>.
        /// </summary>
        public ObservableCollection<PostViewModel> Posts
        {
            get { return _posts; }
        }

        /// <summary>
        /// Gets/Sets the number of new Posts.
        /// </summary>
        public int NewPosts
        {
            get { return _newPosts; }
            set
            {
                if (_newPosts == value)
                    return;

                _newPosts = value;
                RaisePropertyChanged("NewPosts");
            }
        }

        /// <summary>
        /// Gets a value indicating if the channel is loading.
        /// </summary>
        public bool IsLoading
        {
            get { return _isLoading; }
            set
            {
                if (_isLoading == value)
                    return;

                _isLoading = value;
                RaisePropertyChanged("IsLoading");
            }
        }

        /// <summary>
        /// Gets or sets whether this <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance 
        /// is selected in the UI.
        /// </summary>
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                if (value == _isSelected)
                    return;

                _isSelected = value;
                RaisePropertyChanged("IsSelected");
            }
        }

        /// <summary>
        /// Gets the currently selected <see cref="RSSFeedReader.ViewModels.PostViewModel"/>
        /// instance in the collection.
        /// </summary>
        public PostViewModel SelectedPost
        {
            get { return _selectedPost; }
            set
            {
                if (_selectedPost == value)
                    return;

                _selectedPost = value;
                RaisePropertyChanged("SelectedPost");
            }
        }

        /// <summary>
        /// Gets/sets whether the TreeViewItem 
        /// associated with this object is expanded.
        /// </summary>
        public bool IsExpanded
        {
            get { return _isExpanded; }
            set
            {
                if (value != _isExpanded)
                {
                    _isExpanded = value;
                    RaisePropertyChanged("IsExpanded");
                }

                // Expand all the way up to the root.
                if (_isExpanded && _parent != null)
                    _parent.IsExpanded = true;
            }
        }

        /// <summary>
        /// Gets the ObservableCollection of type <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>
        /// that are the children of the current <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        public ObservableCollection<ChannelViewModel> Children
        {
            get { return _children; }
        }

        /// <summary>
        /// Gets the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> parent
        /// of this <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        public ChannelViewModel Parent
        {
            get { return _parent; }
            internal set { _parent = value; }
        }

        /// <summary>
        /// Gets the <see cref="RSSFeedReader.Data.Models.ChannelType"/> of this <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        public ChannelType ChannelType
        {
            get { return _channel.ChannelType; }
        }

        /// <summary>
        /// Returns a System.String that represents the current <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        /// <returns>
        /// The title of this <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </returns>
        public override string ToString()
        {
            return Title;
        }

        #endregion

        #region Public Methods
        /// <summary>
        /// Creates a new ChannelViewModel instance from the channel reference.
        /// </summary>
        /// <param name="channel">The channel to make the ChannelViewModel from.</param>
        /// <returns>A new ChannelViewModel instance.</returns>
        /// <exception cref="System.ArgumentNullException">Null argument passed in.</exception>
        public static ChannelViewModel CreateNew(Channel channel)
        {
            if (channel == null)
                throw new ArgumentNullException("channel");

            ObservableCollection<PostViewModel> posts = new ObservableCollection<PostViewModel>();
            channel.Posts.ForEach(post => posts.Add(new PostViewModel(post)));

            return new ChannelViewModel(channel, posts);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Get number of new posts for this Folder Channel
        /// </summary>
        /// <param name="channel">Channel to iterate through</param>
        private void GetPostsChildrenCount(ChannelViewModel channel)
        {
            foreach (ChannelViewModel cvm in channel.Children)
            {
                _folderNewPosts += cvm.Posts.Count(pvm => pvm.IsRead == false);
                GetPostsChildrenCount(cvm);
            }
        }

        /// <summary>
        /// Update number of new posts.
        /// </summary>
        void UpdateNewPosts(ChannelViewModel cvm, bool updateParent = true)
        {
            if (cvm.ChannelType == ChannelType.Feed)
            {
                cvm.NewPosts = cvm.Posts.Count(post => post.IsRead == false);
                if (cvm.Parent != null && updateParent)
                {
                    cvm.Parent.UpdateFolderNewPosts();
                }
            }
            else
            {
                cvm.UpdateFolderNewPosts();
            }
        }

        /// <summary>
        /// Update number of new posts.
        /// </summary>
        internal void UpdateFolderNewPosts()
        {
            _folderNewPosts = 0;
            GetPostsChildrenCount(this);
            NewPosts = _folderNewPosts;

            if (Parent!= null && Parent.ChannelID != Guid.Empty)
            {
                Parent.UpdateFolderNewPosts();
            }
        }

        /// <summary>
        /// Set the specified ChannelViewModel.IsLoading property to true.
        /// </summary>
        /// <param name="channelViewModel">The ChannelViewModel to change the IsLoading property.</param>
        void SetIsLoading(ChannelViewModel channelViewModel)
        {
            if (channelViewModel.Children.Count > 0)
            {
                foreach (ChannelViewModel channelChild in channelViewModel.Children)
                    SetIsLoading(channelChild);
            }
            else
            {
                if (channelViewModel.ChannelType == ChannelType.Folder)
                    return;

                channelViewModel.IsLoading = true;
            }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Event handler for when a property of the <see cref="RSSFeedReader.ViewModels.PostViewModel"/> changes.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">Event arguments describing the event.</param>
        void OnPostViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            const string isSelected = "IsSelected";
            var postVM = sender as PostViewModel;

            if (postVM != null && (e.PropertyName == isSelected && postVM.IsSelected))
            {
                SelectedPost = postVM;
                UpdateNewPosts(this);
            }
        }

        /// <summary>
        /// Event handler for when the ObservableCollection of type
        /// <see cref="RSSFeedReader.ViewModels.PostViewModel"/> changes.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">Event arguments describing the event.</param>
        void OnPostViewModelCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null && e.NewItems.Count != 0)
                foreach (PostViewModel postViewModel in e.NewItems)
                    postViewModel.PropertyChanged += OnPostViewModelPropertyChanged;

            if (e.OldItems != null && e.OldItems.Count != 0)
                foreach (PostViewModel postViewModel in e.OldItems)
                    postViewModel.PropertyChanged -= OnPostViewModelPropertyChanged;
        }

        /// <summary>
        /// Event handler for when a channel feed is synched.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelSynched(object sender, ChannelSynchronisedEventArgs e)
        {
            UpdateNewPosts(this);
        }

        #endregion

        #region Base Class Overrides

        /// <summary>
        /// Determines whether this instance of <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> and a specified object,
        /// which must also be a <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> object, have the same value.
        /// </summary>
        /// <param name="obj">An System.Object</param>
        /// <returns>true if obj is a <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> and its value is the same as this instance;
        /// otherwise, false.</returns>
        public override bool Equals(object obj)
        {
            ChannelViewModel other = obj as ChannelViewModel;

            if (obj == null || obj.GetType() != typeof(ChannelViewModel))
                return false;

            return other != null && ChannelID == other.ChannelID;
        }

        /// <summary>
        /// Returns the hashcode for this string.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode()
        {
            return ChannelID.GetHashCode();
        }

        #endregion
    }
}
